Spring BootのGetting Startedを軽く読んでみる
丹内です。昨日に引き続きSpring Bootネタです。
本日はGetting Startedのうち、webアプリを作る上で基本的なタイトルの内容を読んでみようと思います。
Getting Startedのフォーマット
インデックスページを見るとたくさんあって読み切れないようにも見えるのですが、詳細ページの構造は以下のようになっています。
- What you’ll build
- What you’ll need
- How to complete this guide
- 本題
- Summary
このうち本題とSummaryが各ドキュメントによって異なります。なので、アプリケーションを作る上で疑問になった点が出たら、そのドキュメントの本題とSummaryだけを見れば済みそうです。
Spring Boot with Docker
ということでいきなりDockerです。
文字列を返す簡単なアプリをセットアップした後、./gradlew build
でビルドして以下のDockerfileでコンテナ化しています。
FROM java:8 VOLUME /tmp ADD gs-spring-boot-docker-0.1.0.jar app.jar RUN bash -c 'touch /app.jar' ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
また、GradleでDockerイメージのビルドを行う設定もしています。
buildscript { ... dependencies { ... classpath('se.transmode.gradle:gradle-docker:1.2') } } group = 'springio' ... apply plugin: 'docker' task buildDocker(type: Docker, dependsOn: build) { push = true applicationName = jar.baseName dockerfile = file('src/main/docker/Dockerfile') doFirst { copy { from jar into stageDir } } }
Spring Cloud + Lattice
またコンテナ絡みです。Latticeという、マイクロサービスで複数コンテナを協調動作させる際のユーティリティとSpring Bootの統合に関するGetting Startedです。
Latticeは以下のことが可能なツールのようです。
- Cluster scheduling
- HTTP load balancing
- Log aggregation
- Health management
このツール単体でも非常に興味深い!
...ドキュメントを読み進めましょう。https://github.com/cloudfoundry-incubator/lattice.gitをgit cloneしてvagrant upでLattice用の環境を作った後、latticeコマンドをセットアップし、コンテナ化したSpring Bootアプリケーションをデプロイしています。
Consuming a RESTful Web Service
SpringのRestTemplateを使ったGetting Startedです。
最初に@JsonIgnoreProperties
をアノテートしたドメインクラスを作り、Application.java
ではRestTemplateを使ってドメインクラスのオブジェクトをJSONに変換してレスポンスにしています。
Building a Hypermedia-Driven RESTful Web Service
HATEOASなサンプルを作成するGetting Startedです。
ResourceSupportをextendして@JsonCreator
をアノテートしたGreeting
ドメインクラスを作り、ControllerとApplication.javaを作って動かすだけで、_links
プロパティが付くハイパーメディアなレスポンスになります。
Building an Application with Spring Boot
Spring Bootによるアプリ作成の流れを一通り書いています。
途中でControllerのテストについて書かれてあります。
testCompile("org.springframework.boot:spring-boot-starter-test")
の依存を追加し、テストを書きます。
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MockServletContext.class) @WebAppConfiguration public class HelloControllerTest { private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build(); } @Test public void getHello() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Greetings from Spring Boot!"))); } }
メソッドチェーンでexpectationしているのが便利そうだと思いました。
Building a RESTful Web Service with Spring Boot Actuator
Spring Boot ActuatorはSpring Bootのサブプロジェクトで、production環境でアプリを動かすために必要なフィーチャーを追加してくれるようです。
org.springframework.boot:spring-boot-starter-actuator
を依存関係に追加するだけで、Spring Bootアプリケーションのレスポンスにタイムスタンプが付いたりします。また、監査やメトリクスなどの機能もあるようです。
Accessing Relational Data using JDBC with Spring
JdbcTemplate
を使ってRDBにアクセスします。
@SpringBootApplication public class Application implements CommandLineRunner { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String args[]) { SpringApplication.run(Application.class, args); } @Autowired JdbcTemplate jdbcTemplate; @Override public void run(String... strings) throws Exception { log.info("Creating tables"); jdbcTemplate.execute("DROP TABLE customers IF EXISTS"); jdbcTemplate.execute("CREATE TABLE customers(" + "id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))"); // Split up the array of whole names into an array of first/last names List<Object[]> splitUpNames = Arrays.asList("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long").stream() .map(name -> name.split(" ")) .collect(Collectors.toList()); // Use a Java 8 stream to print out each tuple of the list splitUpNames.forEach(name -> log.info(String.format("Inserting customer record for %s %s", name[0], name[1]))); // Uses JdbcTemplate's batchUpdate operation to bulk load data jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames); log.info("Querying for customer records where first_name = 'Josh':"); jdbcTemplate.query( "SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" }, (rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name")) ).forEach(customer -> log.info(customer.toString())); } }
さくっとLambdaが使われていて最高だと思います。
Serving Web Content with Spring MVC
今まではJSONばかり返していましたが、このGetting StartedではHTMLを返します。
src/main/resources/templates/greeting.html
として、以下のようにテンプレートを書きます。
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Getting Started: Serving Web Content</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <p th:text="'Hello, ' + ${name} + '!'" /> </body> </html>
Creating an Asynchronous, Event-Driven Application with Reactor
AWS Lambdaのような非同期イベント駆動型アプリケーションを作成するGetting Startedです。
まずReceiverを作ります。
package hello; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import reactor.bus.Event; import reactor.fn.Consumer; import java.util.concurrent.CountDownLatch; @Service class Receiver implements Consumer<Event<Integer>> { @Autowired CountDownLatch latch; RestTemplate restTemplate = new RestTemplate(); public void accept(Event<Integer> ev) { QuoteResource quoteResource = restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", QuoteResource.class); System.out.println("Quote " + ev.getData() + ": " + quoteResource.getValue().getQuote()); latch.countDown(); } }
次にPublisherを作ります。
package hello; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import reactor.bus.Event; import reactor.bus.EventBus; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @Service public class Publisher { @Autowired EventBus eventBus; @Autowired CountDownLatch latch; public void publishQuotes(int numberOfQuotes) throws InterruptedException { long start = System.currentTimeMillis(); AtomicInteger counter = new AtomicInteger(1); for (int i = 0; i < numberOfQuotes; i++) { eventBus.notify("quotes", Event.wrap(counter.getAndIncrement())); } latch.await(); long elapsed = System.currentTimeMillis() - start; System.out.println("Elapsed time: " + elapsed + "ms"); System.out.println("Average time per quote: " + elapsed / numberOfQuotes + "ms"); } }
あとはドメインクラスとApplication.javaですが、Application.javaでは作成したコンポーネントを登録しています。
AWS Lambdaに処理を切り出す前の緩衝材として使えそうです!!
Securing a Web Application
Spring Securityを使ってアプリケーションに認証機能を追加するGetting Startedです。
org.springframework.boot:spring-boot-starter-security
の依存関係を追加して、src/main/java/hello/WebSecurityConfig.java
を作成します。
package hello; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity; @Configuration @EnableWebMvcSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } }
これでOKです。
まとめ
Spring Bootは膨大なSpringプロジェクトの成果を簡単に使えるようにする上、ドキュメントも豊富で良いと思いました。
マイクロサービスのこともよく考えられていて、このエコシステムに乗ると効率よく学習できそうです。